/**
 * \file    description.cpp
 * \brief   Parser generator.
 * \author  Jérôme LAURENS
 * \version 0.1
 * \date    September 19, 2014
 *
 * Reads a LALR(1) grammar, generates a parser based on a template file.
 * This is based on lemon parser generator, but has strongly been
 * revisited.
 *
 */

#include "description.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <boost/range/adaptor/reversed.hpp>

#include "symbol.h"
#include "rule.h"
#include "generator.h"
#include "state.h"
#include "item.h"

LPP_NAMESPACE_BEGIN

Description::Description() {
}

String Description::operator () (ItemRef const & item) const {
    if (!item)
        return "";
    return item->description();
}

String Description::operator () (const SymbolRef & __s, Index const verbosity) const {
    if (!__s) {
        return "NO SYMBOL";
    }
    return operator()(*__s,verbosity);
}

String Description::operator () (const State & __s, Index const verbosity) const {
    std::ostringstream oss;
    if (verbosity>0) {
        oss<<(Format("state #%1%/%2% => ")%__s.index()%__s.id()).str();
    }
    for (ItemRef const & item: __s.core_items()) {
        oss<<Description()(item)<<" / ";
    }
    if (verbosity>1) {
        oss<<"<";
        for (ActionRef const & action: __s.actions()) {
            oss<<Description()(action);
        }
        oss<<">";
    }
    return oss.str();
}

String Description::operator () (const StateRef & __s, Index const verbosity) const {
    if (!__s) {
        return "NO STATE";
    }
    return operator()(*__s,verbosity);
}

String Description::operator () (const Symbol & __s, Index const verbosity) const {
    std::ostringstream oss;
    if (verbosity>0) {
        oss<<(Format("%%%% Symbol(%1%): ")%&__s).str();
    } else {
        oss<<"%%%% Symbol: ";
    }
    oss<<__s.name();
    if (verbosity>0) {
        oss<<(Format(", id=%1%")%__s.id()).str();
    }
    oss<<", type is ";
    switch(__s.type()) {
    case Symbol::Type::TERMINAL:
        oss << "terminal";
        break;
    case Symbol::Type::UNDEFINED:
        oss << "undefined";
        break;
    case Symbol::Type::ACCEPT:
        oss << "accept";
        break;
    case Symbol::Type::NONTERMINAL:
        oss << "non terminal";
        break;
    case Symbol::Type::MULTITERMINAL:
        oss << "multi terminal";
        break;
    case Symbol::Type::DEFAULT:
        oss << "default";
        break;
    case Symbol::Type::ENDOFFILE:
        oss << "end of file";
        break;
    case Symbol::Type::ERROR:
        oss << "error";
        break;
    }
    oss<<" , precedence is "<<__s.precedence()<<", associativity is ";
    switch(__s.associativity()) {
    case Symbol::Associativity::UNKNOWN:
        oss << "unspecified";
        break;
    case Symbol::Associativity::LEFT:
        oss << "left";
        break;
    case Symbol::Associativity::RIGHT:
        oss << "right";
        break;
    case Symbol::Associativity::NONE:
        oss << "none";
        break;
    }
    SymbolRef fallback = __s.fallback();
    if (fallback) {
        oss<<", fallback="<<fallback->name();

    }
    if (__s.can_reduce()) {
        oss<<", can reduce";
    }
    if (__s.is_symbol()) {
        oss<<", is symbol";
    }
    if (__s.is_default()) {
        oss<<", is default";
    }
    if (__s.is_terminal()) {
        oss<<", is terminal";
    }
    if (__s.is_non_terminal()) {
        oss<<", is non terminal";
        oss<<", rules: "<<__s.number_of_rules();
    }
    if (__s.is_multi_terminal()) {
        oss<<", is multi terminal";
        oss<<std::endl<<"%% "<<__s.number_of_sub_symbols()<<" sub symbols:";
#if 1
        oss<<std::endl<<__s.name()<<" ::= ";
        String separator="";
        for(SymbolRef const & s: __s.get_sub_symbols()) {
           if (s) {
                oss<<separator<<s->name();
                separator="|";
            }
        }
#else
        String::size_type width = 0;
        for(SymbolRef const & s: __s.sub_symbols()) {
            try {
                auto size = s.name.length();
                if (size < 40 && size>width) {
                    width = size;
                }
            }
            catch(std::exception e){}
        }
        ++width; // <= 40
        String::size_type max_cols = 80 / width; // >= 2
        String::size_type n_col = 0;
        for(SymbolRef const & sR: __s.sub_symbols_m) {
            try {
                auto size = sR.name_m.length();
                if (size<40) {
                    oss<<std::setw(width)<<std::left<<sR.name_m;
                    if (++n_col==max_cols) {
                        oss<<std::endl;
                        n_col = 0;
                    }
                } else {
                    oss<<std::endl<<sR.name_m<<std::endl;
                    n_col = 0;
                }
            }
            catch(std::exception e){}
        }
#endif
    }
    if (__s.has_shortcut()) {
        oss<<", shortcut: "<<__s.shortcut();
    }
    return oss.str();
}

String Description::operator () (const RuleRef & __r, Index const verbosity) const {
    if (!__r) {
        return "No rule";
    }
    return operator () (*__r,verbosity);
}

String Description::operator () (const Rule & __r, Index const verbosity) const {
    std::ostringstream oss;
    if (verbosity>1) {
        oss<<"%% Rule: "<<&__r;
        if(verbosity>2) {
            oss<<"("<<__r.id()<<"/"<<__r.index()<<")";
        }
        oss<< std::endl;
        oss<<"line "<<__r.rule_line()<<" "<<__r.rhs_symbols().size()<< std::endl;
    }
    SymbolRef const & symbol = __r.lhs_symbol();
    if (symbol) {
        oss << symbol->name();
    } else {
        oss << "__VOID SYMBOL__";
    }
    if (verbosity>0) {
        if (!__r.lhs_alias().empty() && !std::regex_match(__r.lhs_alias(), std::regex("^\\${2}$"))) {
            oss<<__r.lhs_alias();
        }
    }
    oss<<" ::= ";
    SymbolIndex i = 0;
    String separator = "";
    for (SymbolRef const & symbol: __r.rhs_symbols()) {
        oss << separator;
        if (symbol) {
            if (symbol->is_multi_terminal()) {
                String sep = "";
                for (SymbolRef const & S: symbol->get_sub_symbols()) {
                    oss<<sep<<S->short_name();
                    sep="|";
                }
            } else {
                oss<<symbol->short_name();
            }
        } else {
            oss << "__VOID SYMBOL__";
        }
        separator = " ";
        if (verbosity>0) {
            try {
                String const alias = __r.rhs_alias_at(i);
                if (!alias.empty() && !std::regex_match(alias, "^\\$\\d+$"_regex)) {
                    oss << alias;
                }
            }
            catch (std::exception e) {
                oss << " $ __BAD_ALIAS__ ";
            }
        }
        ++i;
    }
    if (__r.precedence_symbol()) {
        oss << " [" << __r.precedence_symbol()->name() <<"]";
    }
    if (verbosity>1 && __r.rhs_symbols().size()) {
        oss<<std::endl<<(Format("(%1%) := ")%__r.lhs_symbol().get()).str();
        for (SymbolRef const & symbol: __r.rhs_symbols()) {
            oss<<(Format("(%1%)/")%symbol.get()).str();
        }
    }
    if (verbosity>1) {
        for (String const & line: __r.code_template_lines()) {
            oss << std::endl << line;
        }
    }
    if (verbosity>0) {
        for (String const & line: __r.code_lines()) {
            oss << std::endl << line;
        }
    }
    return oss.str();
}

/* Generate the "y.output" log file */
String Description::operator()(Generator const & g, bool only_core_items) const {
#if 0
    String filename = std::regex_replace(input_file_name,std::regex("\\.[^\\.]+"), ".out");
    ofstream myfile(filename);
    if (!myfile.is_open()) {
        std::cerr << "Failed to open file for writing "<< filename << std::endl;
        return;
    }
#endif
    std::ostringstream oss;
    Index i = 0;
    for (auto const & state: g.states()) {
        oss<<"State "<<i++<<"/"<<state->id()<<"/"<<state->index()<<std::endl;
        ItemRefSet const & list = only_core_items? state->core_items(): state->closure_items();
        oss<<std::setw(8)<<std::left;
        for (auto const & item: list) {
            if (item->is_at_end()) {
                oss<<item->rule()->index();
            } else {
                oss<<" ";
            }
            oss<<operator()(item)<<std::endl;
        }
        for (ActionRef const & action: state->actions()) {
            String d = operator()(action,20,true);
            if (d.length()) {
                oss<<d<<std::endl;
            }
        }
    }
    oss<<"Symbols:"<<std::endl;
    i = 0;
    for (auto const & S: g.symbols()) {// ordered symbols
        oss<<std::setw(8)<<i++<<"  "<<operator()(S);
    }
    return oss.str();
}

String Description::operator()(Action const & action
                               , String::size_type indent
                               , bool show_conflicts) const {
    return action.description(indent,show_conflicts);
}

String Description::operator()(ActionRef const & action
                               , String::size_type indent
                               , bool show_conflicts) const {
    if (action) {
        return operator()(*action,indent,show_conflicts);
    }
    return "No action";
}

String Description::operator()(Grammar const & grammar, Index const verbosity) const {
    std::ostringstream oss;
    if (!verbosity) {
        std::ostringstream oss;
        Index max_length = 10;
        for (SymbolRef const & symbol: grammar.terminals()) {
            Index length = symbol->name().length();
            if (length>max_length) max_length=length;
        }
        Index n_columns = 76/(max_length+5);
        if (!n_columns) n_columns = 1;

        oss<<"%% terminals:"<<std::endl;
        Index n = 0;
        for (SymbolRef const & symbol: grammar.terminals()) {
            oss<<std::setw(max_length+1)<<std::left<<symbol->name();
            if (++n==n_columns) {
                n = 0;
                oss<<std::endl;
            }
        }
        if(n) oss<<std::endl;
        max_length = 10;
        for (SymbolRef const & symbol: grammar.non_terminals()) {
            Index length = symbol->name().length();
            if (length>max_length) max_length=length;
        }
        n_columns = 76/(max_length+5);
        if (!n_columns) n_columns = 1;
        oss<<"%% non terminals:"<<std::endl;
        n = 0;
        for (SymbolRef const & symbol: grammar.non_terminals()) {
            oss<<std::setw(max_length+1)<<std::left<<symbol->name();
            if (++n==n_columns) {
                n = 0;
                oss<<std::endl;
            }
        }
        oss<<"%% rules:"<<std::endl;
        if(n) oss<<std::endl;
        for (RuleRef const & rule: grammar.rules()) {
            oss<<Description()(rule)<<std::endl;
        }
        return oss.str();
    }
    SymbolRefList SRs;
    oss<<"%% terminals:"<<std::endl;
    SRs = grammar.terminals();
    std::sort(SRs.begin(),SRs.end(),[](SymbolRef const & lhs, SymbolRef const & rhs)->bool{
        return lhs->name()<rhs->name();
    });
    for (SymbolRef const & symbol: SRs) {
        if (verbosity>1) {
            oss<<(Format("%%terminal(%1%,%2%): ")%&symbol%symbol.get()).str();
        }
        oss<<Description()(symbol)<<std::endl;
    }
    oss<<"%% multi terminals:"<<std::endl;
    SRs = grammar.multi_terminals();
    std::sort(SRs.begin(),SRs.end(),[](SymbolRef const & lhs, SymbolRef const & rhs)->bool{
        return lhs->name()<rhs->name();
    });
    for (SymbolRef const & symbol: SRs) {
        if (verbosity>1) {
            oss<<(Format("%%multi terminal(%1%,%2%): ")%&symbol%symbol.get()).str();
        }
        oss<<Description()(symbol)<<std::endl;
    }
    oss<<"%% non terminals:"<<std::endl;
    SRs = grammar.non_terminals();
    std::sort(SRs.begin(),SRs.end(),[](SymbolRef const & lhs, SymbolRef const & rhs)->bool{
        return lhs->name()<rhs->name();
    });
    for (SymbolRef const & symbol: SRs) {
        if (verbosity>1) {
            oss<<(Format("%%non terminal(%1%,%2%): ")%&symbol%symbol.get()).str();
        }
        oss<<Description()(symbol)<<std::endl;
    }
    oss<<"%% rules:"<<std::endl;
    RuleRefList L(grammar.rules().begin(),grammar.rules().end());
    std::sort(L.begin(),L.end(),[](RuleRef const & l, RuleRef const & r){return r<l;});
    for (RuleRef const & rule: L) {
        oss<<Description()(rule,verbosity)<<std::endl;
    }
    return oss.str();
}

String Description::operator()(GrammarRef const & grammar, Index const verbosity) const {
    if (grammar) {
        return operator()(*grammar,verbosity);
    }
    return "No grammar given";
}

LPP_NAMESPACE_END
